package utils

import (
	"fmt"
	"net/http"
	"strings"
)

// AuthHeaderInjector handles injecting authentication information into HTTP requests
type AuthHeaderInjector struct{}

// NewAuthHeaderInjector creates a new AuthHeaderInjector instance
func NewAuthHeaderInjector() *AuthHeaderInjector {
	return &AuthHeaderInjector{}
}

// InjectBearerToken injects a Bearer token into the Authorization header
func (ahi *AuthHeaderInjector) InjectBearerToken(req *http.Request, token string) {
	if req == nil {
		return
	}
	
	// Clean the token (remove any existing Bearer prefix)
	cleanToken := strings.TrimSpace(token)
	if strings.HasPrefix(strings.ToLower(cleanToken), "bearer ") {
		cleanToken = strings.TrimSpace(cleanToken[7:])
	}
	
	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", cleanToken))
}

// InjectAPIKey injects an API key into a custom header
func (ahi *AuthHeaderInjector) InjectAPIKey(req *http.Request, headerName, token string) {
	if req == nil || headerName == "" {
		return
	}
	
	req.Header.Set(headerName, strings.TrimSpace(token))
}

// InjectQueryParam adds a token to the URL query parameters
func (ahi *AuthHeaderInjector) InjectQueryParam(req *http.Request, paramName, token string) {
	if req == nil || paramName == "" {
		return
	}
	
	// Parse the existing URL
	u := req.URL
	if u == nil {
		return
	}
	
	// Get existing query parameters
	query := u.Query()
	
	// Add the token parameter
	query.Set(paramName, strings.TrimSpace(token))
	
	// Update the URL with the new query parameters
	u.RawQuery = query.Encode()
}

// InjectCustomAuth injects authentication using custom format based on configuration
func (ahi *AuthHeaderInjector) InjectCustomAuth(req *http.Request, config *AuthConfig, token string) error {
	if req == nil || config == nil {
		return NewAuthError(ErrTypeConfigInvalid, "request and config cannot be nil", "")
	}
	
	cleanToken := strings.TrimSpace(token)
	if cleanToken == "" {
		return NewAuthError(ErrTypeTokenInvalid, "token cannot be empty", "")
	}
	
	// Apply token prefix if specified
	finalToken := cleanToken
	if config.TokenPrefix != "" {
		// Remove existing prefix if it matches
		if strings.HasPrefix(cleanToken, config.TokenPrefix) {
			finalToken = cleanToken
		} else {
			finalToken = config.TokenPrefix + cleanToken
		}
	}
	
	// Inject based on configuration
	if config.HeaderName != "" {
		req.Header.Set(config.HeaderName, finalToken)
	} else if config.QueryParam != "" {
		ahi.InjectQueryParam(req, config.QueryParam, finalToken)
	} else {
		return NewAuthError(ErrTypeConfigInvalid, 
			"custom auth format requires either header_name or query_param", "")
	}
	
	return nil
}

// InjectAuthHeaders is the main method that injects authentication headers based on auth states and config
func (ahi *AuthHeaderInjector) InjectAuthHeaders(req *http.Request, authStates map[string]*AuthState, config *AuthConfig) error {
	if req == nil {
		return NewAuthError(ErrTypeConfigInvalid, "request cannot be nil", "")
	}
	
	if config == nil || len(config.Requires) == 0 {
		// No authentication required
		return nil
	}
	
	if authStates == nil {
		return NewAuthError(ErrTypeAuthRequired, 
			"authentication required but no auth states available", 
			fmt.Sprintf("required auth types: %v", config.Requires))
	}
	
	// Process each required authentication type
	for _, authType := range config.Requires {
		authState, exists := authStates[authType]
		if !exists {
			return NewAuthError(ErrTypeAuthRequired,
				fmt.Sprintf("required authentication type '%s' not found", authType),
				"please authenticate first")
		}
		
		if authState.IsExpired() {
			return NewAuthError(ErrTypeTokenExpired,
				fmt.Sprintf("authentication token for '%s' has expired", authType),
				"please re-authenticate")
		}
		
		// Inject the token based on its type
		switch authState.TokenType {
		case "bearer":
			ahi.InjectBearerToken(req, authState.Token)
			
		case "api_key":
			// For API key, we need header name from metadata or config
			headerName := authState.Metadata["header_name"]
			if headerName == "" {
				// Fallback to common API key header names
				headerName = "X-API-Key"
			}
			ahi.InjectAPIKey(req, headerName, authState.Token)
			
		case "custom":
			// For custom auth, create a temporary config from auth state metadata
			customConfig := &AuthConfig{
				TokenFormat: "custom",
				HeaderName:  authState.Metadata["header_name"],
				QueryParam:  authState.Metadata["query_param"],
				TokenPrefix: authState.Metadata["token_prefix"],
			}
			
			if err := ahi.InjectCustomAuth(req, customConfig, authState.Token); err != nil {
				return fmt.Errorf("failed to inject custom auth for '%s': %w", authType, err)
			}
			
		default:
			return NewAuthError(ErrTypeTokenInvalid,
				fmt.Sprintf("unsupported token type '%s' for auth type '%s'", authState.TokenType, authType),
				"supported types are: bearer, api_key, custom")
		}
	}
	
	return nil
}